/*
 * Copyright (c) 2018, Nordic Semiconductor
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package no.nordicsemi.android.nrfmesh.ble;

import android.Manifest;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import android.text.TextUtils;
import android.view.MenuItem;
import android.view.View;

import android.widget.EditText;
import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityCompat;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.SimpleItemAnimator;
import dagger.hilt.android.AndroidEntryPoint;
import no.nordicsemi.android.nrfmesh.ProvisioningActivity;
import no.nordicsemi.android.nrfmesh.R;
import no.nordicsemi.android.nrfmesh.adapter.ExtendedBluetoothDevice;
import no.nordicsemi.android.nrfmesh.ble.adapter.DevicesAdapter;
import no.nordicsemi.android.nrfmesh.databinding.ActivityScannerBinding;
import no.nordicsemi.android.nrfmesh.provisioners.MeshHeadlessProvisioner;
import no.nordicsemi.android.nrfmesh.utils.ProvisionerStates;
import no.nordicsemi.android.nrfmesh.utils.Utils;
import no.nordicsemi.android.nrfmesh.viewmodels.ScannerStateLiveData;
import no.nordicsemi.android.nrfmesh.viewmodels.ScannerViewModel;

@AndroidEntryPoint
public class ScannerActivity extends AppCompatActivity implements
        DevicesAdapter.OnItemClickListener {
    private static final int REQUEST_ACCESS_FINE_LOCATION = 1022; // random number
    private static final int REQUEST_ACCESS_BLUETOOTH_PERMISSION = 1023; // random number

    private ActivityScannerBinding binding;
    private ScannerViewModel mViewModel;
    private boolean mScanWithProxyService;
    private DevicesAdapter adapter;  //适配器提为字段，便于在配网期间禁用点击
    private MeshHeadlessProvisioner headless;  //无界面配网控制器

//    private final ActivityResultLauncher<Intent> provisioner = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
//        if (result.getResultCode() == RESULT_OK && result.getData() != null) {
//            setResultIntent(result.getData());
//        }
//    });

    private final ActivityResultLauncher<Intent> enableBluetooth = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
        if (result.getResultCode() == RESULT_OK && result.getData() != null) {
            startScan(mViewModel.getScannerRepository().getScannerState());
        }
    });

    private final ActivityResultLauncher<Intent> reconnect = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
        if (result.getResultCode() == RESULT_OK && result.getData() != null) {
            finish();
        }
    });

    @Override
    protected void onCreate(@Nullable final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityScannerBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());
        // Create view model containing utility methods for scanning
        mViewModel = new ViewModelProvider(this).get(ScannerViewModel.class);

        final Toolbar toolbar = binding.toolbar;
        toolbar.setTitle(R.string.title_scanner);
        setSupportActionBar(toolbar);
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);

        if (getIntent() != null) {
            mScanWithProxyService = getIntent().getBooleanExtra(Utils.EXTRA_DATA_PROVISIONING_SERVICE, true);
            if (mScanWithProxyService) {
                getSupportActionBar().setSubtitle(R.string.sub_title_scanning_nodes);
            } else {
                getSupportActionBar().setSubtitle(R.string.sub_title_scanning_proxy_node);
            }
        }

        // Configure the recycler view
        final RecyclerView recyclerViewDevices = binding.recyclerViewBleDevices;
        recyclerViewDevices.setLayoutManager(new LinearLayoutManager(this));
        final DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recyclerViewDevices.getContext(), DividerItemDecoration.VERTICAL);
        recyclerViewDevices.addItemDecoration(dividerItemDecoration);

        final SimpleItemAnimator itemAnimator = (SimpleItemAnimator) recyclerViewDevices.getItemAnimator();
        if (itemAnimator != null) itemAnimator.setSupportsChangeAnimations(false);

        adapter = new DevicesAdapter(this, mViewModel.getScannerRepository().getScannerResults());
        adapter.setOnItemClickListener(this);
        recyclerViewDevices.setAdapter(adapter);

        binding.noDevices.actionEnableLocation.setOnClickListener(v -> onEnableLocationClicked());
        binding.bluetoothOff.actionEnableBluetooth.setOnClickListener(v -> onEnableBluetoothClicked());
        binding.noLocationPermission.actionGrantLocationPermission.setOnClickListener(v -> onGrantLocationPermissionClicked());
        binding.noLocationPermission.actionPermissionSettings.setOnClickListener(v -> onPermissionSettingsClicked());
        binding.noBluetoothPermissions.actionGrantBluetoothPermission.setOnClickListener(v -> onGrantBluetoothPermissionClicked());

        mViewModel.getScannerRepository().getScannerState().observe(this, this::startScan);

        //初始化无界面配网控制器（所有 UI 反馈都留在本页）
        headless = new MeshHeadlessProvisioner(
                this,
                this,
                new MeshHeadlessProvisioner.Callbacks() {
                    @Override public void onProvisionStart(ExtendedBluetoothDevice d) {
                        // 停止扫描，显示进度，禁用点击
                        stopScan();
                        setBusyUi(true, "正在配置 " + safeName(d));
                    }
                    @Override public void onProgressStateChanged(ProvisionerStates s) {
                        // 可选：显示状态到副标题或状态条
                        //getSupportActionBar().setSubtitle("状态: " + s.name());
                    }
                    @Override public void onRequireOobInput(@NonNull MeshHeadlessProvisioner.OobRequest req) {
                        showOobDialog(req);
                    }
                    @Override public void onFailed(@NonNull String reason) {
                        setBusyUi(false, null);
                        Toast.makeText(ScannerActivity.this, "配网失败: " + reason, Toast.LENGTH_LONG).show();
                        // 失败后恢复扫描
                        mViewModel.getScannerRepository().getScannerState().startScanning();
                    }
                    @Override public void onCompleted(@NonNull Intent resultIntent) {
                        setBusyUi(false, null);
                        Toast.makeText(ScannerActivity.this, "配置完成", Toast.LENGTH_SHORT).show();
                        // 成功后恢复扫描（也可以在此刷新你自己的列表）
                        mViewModel.getScannerRepository().getScannerState().startScanning();
                    }
                    @Override public void onLog(@NonNull String line) {
                        // 可加 Log.d("HeadlessProv", line);
                    }
                }
        );
    }

    @Override
    protected void onStart() {
        super.onStart();
        if (headless == null || !headless.isRunning()) {
            mViewModel.getScannerRepository().getScannerState().startScanning();
        }
    }

    @Override
    protected void onStop() {
        super.onStop();
        stopScan();
        if (headless != null && headless.isRunning()) {
            headless.cancel();
        }
    }

    @Override
    public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
        if (item.getItemId() == android.R.id.home) {
            onBackPressed();
            return true;
        }
        return false;
    }

    @Override
    public void onItemClick(final ExtendedBluetoothDevice device) {
        //配网中直接忽略点击，避免并发
        if (headless != null && headless.isRunning()) return;

        //We must disconnect from any nodes that we are connected to before we start scanning.
        if (mViewModel.getBleMeshManager().isConnected())
            mViewModel.disconnect();

        if (mScanWithProxyService) {  //点击未配网节点
            MeshHeadlessProvisioner.Options opts = MeshHeadlessProvisioner.Options.noOob(makeDefaultNodeName(device));

            //启动无界面配网
            headless.start(device, opts);
        } else {
            Intent intent = new Intent(this, ReconnectActivity.class);
            intent.putExtra(Utils.EXTRA_DEVICE, device);
            reconnect.launch(intent);
        }
    }

    @Override
    public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == REQUEST_ACCESS_FINE_LOCATION) {
            mViewModel.getScannerRepository().getScannerState().startScanning();
        } else if (requestCode == REQUEST_ACCESS_BLUETOOTH_PERMISSION) {
            mViewModel.getScannerRepository().getScannerState().startScanning();
        }
    }

    private void onEnableLocationClicked() {
        final Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
        startActivity(intent);
    }

    private void onEnableBluetoothClicked() {
        final Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        enableBluetooth.launch(enableIntent);
    }

    private void onGrantLocationPermissionClicked() {
        Utils.markLocationPermissionRequested(this);
        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, REQUEST_ACCESS_FINE_LOCATION);
    }

    private void onGrantBluetoothPermissionClicked() {
        if (Utils.isSorAbove()) {
            Utils.markBluetoothPermissionsRequested(this);
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.BLUETOOTH_CONNECT}, REQUEST_ACCESS_FINE_LOCATION);
        }
    }

    private void onPermissionSettingsClicked() {
        final Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
        intent.setData(Uri.fromParts("package", getPackageName(), null));
        startActivity(intent);
    }

    /**
     * Start scanning for Bluetooth devices or displays a message based on the scanner state.
     */
    private void startScan(final ScannerStateLiveData state) {

        //配网期间不更新扫描 UI，保持“忙碌”
        if (headless != null && headless.isRunning()) {
            binding.stateScanning.setVisibility(View.VISIBLE);
            binding.noDevices.getRoot().setVisibility(View.GONE);
            binding.bluetoothOff.getRoot().setVisibility(View.GONE);
            binding.noLocationPermission.getRoot().setVisibility(View.GONE);
            binding.noBluetoothPermissions.getRoot().setVisibility(View.GONE);
            return;
        }

        if (Utils.isBluetoothScanAndConnectPermissionsGranted(this)) {
            binding.noBluetoothPermissions.getRoot().setVisibility(View.GONE);
            // First, check the Location permission. This is required on Marshmallow onwards in order to scan for Bluetooth LE devices.
            if (Utils.isLocationPermissionsGranted(this)) {
                binding.noLocationPermission.getRoot().setVisibility(View.GONE);

                // Bluetooth must be enabled
                if (state.isBluetoothEnabled()) {
                    binding.bluetoothOff.getRoot().setVisibility(View.GONE);

                    if (!state.isScanning()) {
                        // We are now OK to start scanning
                        if (mScanWithProxyService) {
                            mViewModel.getScannerRepository().startScan(BleMeshManager.MESH_PROVISIONING_UUID);
                        } else {
                            mViewModel.getScannerRepository().startScan(BleMeshManager.MESH_PROXY_UUID);
                        }
                        binding.stateScanning.setVisibility(View.VISIBLE);
                    }

                    if (state.isEmpty()) {
                        binding.noDevices.getRoot().setVisibility(View.VISIBLE);

                        if (!Utils.isLocationRequired(this) || Utils.isLocationEnabled(this)) {
                            binding.noLocationPermission.getRoot().setVisibility(View.INVISIBLE);
                        } else {
                            binding.noLocationPermission.getRoot().setVisibility(View.VISIBLE);
                        }
                    } else {
                        binding.noDevices.getRoot().setVisibility(View.GONE);
                    }
                } else {
                    binding.bluetoothOff.getRoot().setVisibility(View.VISIBLE);
                    binding.stateScanning.setVisibility(View.INVISIBLE);
                    binding.noDevices.getRoot().setVisibility(View.GONE);
                }
            } else {
                binding.noLocationPermission.getRoot().setVisibility(View.VISIBLE);
                binding.bluetoothOff.getRoot().setVisibility(View.GONE);
                binding.stateScanning.setVisibility(View.INVISIBLE);
                binding.noDevices.getRoot().setVisibility(View.GONE);

                final boolean deniedForever = Utils.isLocationPermissionDeniedForever(this);
                binding.noLocationPermission.actionGrantLocationPermission.setVisibility(deniedForever ? View.GONE : View.VISIBLE);
                binding.noLocationPermission.actionPermissionSettings.setVisibility(deniedForever ? View.VISIBLE : View.GONE);
            }
        } else {
            binding.noBluetoothPermissions.getRoot().setVisibility(View.VISIBLE);
            binding.bluetoothOff.getRoot().setVisibility(View.GONE);
            binding.stateScanning.setVisibility(View.INVISIBLE);
            binding.noDevices.getRoot().setVisibility(View.GONE);

            if(Utils.isSorAbove()){
                final boolean deniedForever = Utils.isBluetoothPermissionDeniedForever(this);
                binding.noBluetoothPermissions.actionGrantBluetoothPermission.setVisibility(deniedForever ? View.GONE : View.VISIBLE);
                binding.noLocationPermission.actionPermissionSettings.setVisibility(deniedForever ? View.VISIBLE : View.GONE);
            }

        }
    }

    /**
     * stop scanning for bluetooth devices.
     */
    private void stopScan() {
        mViewModel.getScannerRepository().stopScan();
    }

    private void setResultIntent(final Intent data) {
        setResult(Activity.RESULT_OK, data);
        finish();
    }

    private String makeDefaultNodeName(final ExtendedBluetoothDevice d) {
        final String name = d.getName() != null ? d.getName() : "Node";
        final String addrTail = d.getAddress() != null
                ? d.getAddress().replace(":", "")
                : String.valueOf(System.currentTimeMillis() % 10000);
        return name + "-" + addrTail.substring(Math.max(0, addrTail.length() - 4));
    }

    //工具：在配网期间给个“忙碌”提示，禁用点击
    private void setBusyUi(boolean busy, @Nullable String subtitle) {
        binding.stateScanning.setVisibility(busy ? View.VISIBLE : View.INVISIBLE);
        // 禁用/恢复点击
        adapter.setOnItemClickListener(busy ? null : this);
        if (!TextUtils.isEmpty(subtitle)) {
            getSupportActionBar().setSubtitle(subtitle);
        } else {
            getSupportActionBar().setSubtitle(mScanWithProxyService
                    ? R.string.sub_title_scanning_nodes
                    : R.string.sub_title_scanning_proxy_node);
        }
    }

    //OOB 输入弹窗（静态/Input/Output 共用）
    private void showOobDialog(MeshHeadlessProvisioner.OobRequest req) {
        final EditText et = new EditText(this);
        et.setHint("输入 OOB/PIN");
        String msg;
        if (req.isStatic) msg = "请输入静态 OOB";
        else if (req.isInput) msg = "请输入设备要求的 PIN";
        else if (req.isOutput) msg = "请输入设备显示/反馈的值";
        else msg = "请输入 OOB";

        new AlertDialog.Builder(this)
                .setTitle("需要 OOB 验证")
                .setMessage(msg)
                .setView(et)
                .setNegativeButton(android.R.string.cancel, (d, w) -> {
                    if (headless != null) headless.cancel();
                })
                .setPositiveButton(android.R.string.ok, (d, w) -> {
                    if (headless != null) headless.submitPin(et.getText().toString().trim());
                })
                .show();
    }

    private String safeName(ExtendedBluetoothDevice d) {
        return (d.getName() != null ? d.getName() : "Node") + "/" + (d.getAddress() != null ? d.getAddress() : "--");
    }
}
